#include "fbo.h"

FrameBufferObject::FrameBufferObject()
{
	this->frame_buffer_handle = (GLuint) -1;
	this->depth_buffer_handle = (GLuint) -1;
	this->texture_handles = NULL;
}

GLuint FrameBufferObject::GetFrameBufferHandle()
{
	return frame_buffer_handle;
}

GLuint FrameBufferObject::GetDepthBufferHandle()
{
	return depth_buffer_handle;
}

GLuint * FrameBufferObject::GetTextureHandles()
{
	return texture_handles;
}

glm::ivec2 FrameBufferObject::GetSize()
{
	return size;
}

int FrameBufferObject::GetNumColorAttachments()
{
	return num_color_attachments;
}

void FrameBufferObject::Bind(int color_attachment_index)
{
	if (color_attachment_index <= this->num_color_attachments && this->frame_buffer_handle != (GLuint) -1)
	{
		// reference: http://www.opengl.org/sdk/docs/man4/xhtml/glBindFramebuffer.xml
		// reference: http://www.opengl.org/sdk/docs/man/xhtml/glDrawBuffer.xml
		glBindFramebuffer(GL_FRAMEBUFFER, this->frame_buffer_handle);
		glDrawBuffer(GL_COLOR_ATTACHMENT0 + color_attachment_index);
	}
}

void FrameBufferObject::Unbind()
{
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void FrameBufferObject::TakeDown()
{
	if (this->frame_buffer_handle != (GLuint) -1)
	{
		glDeleteFramebuffers(1, &this->frame_buffer_handle);
	}

	if (this->depth_buffer_handle != (GLuint) -1)
	{
		glDeleteFramebuffers(1, &this->depth_buffer_handle);
	}

	if (this->texture_handles != NULL)
	{
		glDeleteTextures(this->num_color_attachments, this->texture_handles);
		delete [] this->texture_handles;
	}
}

bool FrameBufferObject::Initialize(glm::ivec2 size, int num_color_attachments, bool allocate_depth_buffer)
{
	this->size = size;
	this->num_color_attachments = num_color_attachments;
	this->texture_handles = new GLuint[num_color_attachments];

	// initialize texture handles
	for (int i = 0; i < num_color_attachments; i++)
	{
		this->texture_handles[i] = (GLuint) -1;
	}

	if (CheckGLErrors())
	{
		return false;
	}

	// reference: http://www.opengl.org/sdk/docs/man4/xhtml/glGenFramebuffers.xml
	glGenFramebuffers(1, &this->frame_buffer_handle);
	glBindFramebuffer(GL_FRAMEBUFFER, this->frame_buffer_handle);

	if (CheckGLErrors())
	{
		return false;
	}

	// generate textures
	// reference: http://www.opengl.org/sdk/docs/man/xhtml/glGenTextures.xml
	glGenTextures(num_color_attachments, this->texture_handles);

	for (int i = 0; i < num_color_attachments; i++)
	{
		// reference: http://www.opengl.org/sdk/docs/man/xhtml/glBindTexture.xml
		// reference: http://www.opengl.org/sdk/docs/man/xhtml/glTexImage2D.xml
		// reference: http://www.opengl.org/sdk/docs/man/xhtml/glTexParameter.xml
		// reference: http://www.khronos.org/opengles/documentation/opengles1_0/html/glTexEnv.html
		// reference: http://www.opengl.org/sdk/docs/man/xhtml/glFramebufferTexture.xml
		glBindTexture(GL_TEXTURE_2D, this->texture_handles[i]);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, this->size.x, this->size.y, 0, GL_RGBA, GL_FLOAT, NULL);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); 
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); 
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 
		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, this->texture_handles[i], 0);
	}

	GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

	if (status != GL_FRAMEBUFFER_COMPLETE)
	{
		return false;
	}

	// allocate a depth buffer
	if (allocate_depth_buffer)
	{
		// reference: http://www.khronos.org/opengles/sdk/docs/man/xhtml/glGenRenderbuffers.xml
		// reference: http://www.khronos.org/opengles/sdk/docs/man/xhtml/glBindRenderbuffer.xml
		// reference: http://www.khronos.org/opengles/sdk/docs/man/xhtml/glRenderbufferStorage.xml
		// reference: http://www.khronos.org/opengles/sdk/docs/man/xhtml/glFramebufferRenderbuffer.xml
		glGenRenderbuffers(1, &this->depth_buffer_handle);
		glBindRenderbuffer(GL_RENDERBUFFER, this->depth_buffer_handle);
		glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, this->size.x, this->size.y);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, this->depth_buffer_handle);
		glBindRenderbuffer(GL_RENDERBUFFER, 0);
	}

	status = glCheckFramebufferStatus(GL_FRAMEBUFFER);

	if (status != GL_FRAMEBUFFER_COMPLETE)
	{
		return false;
	}

	glBindFramebuffer(GL_FRAMEBUFFER, 0);

	return true;
}

bool FrameBufferObject::CheckGLErrors()
{
	bool error_found = false;

	while (glGetError() != GL_NO_ERROR)
	{
		error_found = true;
	}

	return error_found;
}